package vip.xiaonuo.modular.databaseinfo.service.impl;

import javafx.util.Pair;
import vip.xiaonuo.modular.databaseinfo.service.DatabaseInfoService;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public abstract class AbstractDBTableInfoUtil extends AbstractTableInfoUtil implements DatabaseInfoService {
    private static final String TABLE = "TABLE";
    private static final String TABLE_SCHEMA = "TABLE_SCHEM";
    private static final String TABLE_NAME = "TABLE_NAME";
    private static final String COLUMN_NAME = "COLUMN_NAME";
    private static final String COLUMN_SIZE = "COLUMN_SIZE";
    private static final String TYPE_NAME = "TYPE_NAME";
    private static final String COLUMN_DEF = "COLUMN_DEF";

    /**
     * 需要排除的 schema。
     * 一般是那些系统的schema。
     * 在子类中具体指定。
     */
    protected String excludeSchema;

    public AbstractDBTableInfoUtil(final BiDataSource dataSource) {
        super(dataSource);
    }

    // util 方法，用于将 "%" 和 "_" 进行转义
    protected static String escapeWildcard(String source, String escape) {
        if (null == source || "".equals(source)) {
            return null;
        }
        String result = source.replace("%", (escape + "%"));
        result = result.replace("_", (escape + "_"));
        return result;
    }

    // 获取有权限访问的所有表的总数
    @Override
    public int getTableCount(final BiDataSource dataSource) {
        int total = 0;

        // 由子类生成 获取总数 的SQL 语句
        String sql = getTableCountSQL();

        try (Connection connection = DriverManager.getConnection(dataSource.url, dataSource.username, dataSource.password);
             PreparedStatement ps = connection.prepareStatement(sql)) {

            ResultSet countResult = ps.executeQuery();

            if (countResult.next()) {
                total = countResult.getInt(1);
            }
        } catch (Exception e) {

        }
        return total;
    }

    // 获取表的总数的SQL。由子类实现。
    protected abstract String getTableCountSQL();

    // 获取表的所有字段
    // 所有数据库都会提供相应的 JDBC driver，
    // 所以我们只要通过 JDBC 中的 getColumns() 方法，就可以获取表中的所有字段
    @Override
    protected List getTableFields(final BiDataSource dataSource, final String schemaName, final String tableName) {
        List list = new ArrayList();

        try (Connection connection = DriverManager.getConnection(dataSource.url, dataSource.username, dataSource.password)) {

            DatabaseMetaData metaData = connection.getMetaData();

            // 注意，此处 需要对 schema 和 table 的名称进行 "%" 和 "_" 字符的转义
            final String schema = escapeWildcard(schemaName, metaData.getSearchStringEscape());
            final String table = escapeWildcard(tableName, metaData.getSearchStringEscape());

            ResultSet resultSet;

            SourceType sourceType = this.getSourceType();

            // mysql 和 gbase 与其他DB的处理方式不一样
            // 为啥不一样?如果不这么处理，那么，mysql 将无法获取 jdbc url 指定的schema 以外的schema 中的表的字段
            // 比如，给定一个jdbc url: jdbc:mysql://localhost:3306/test
            // 那么，我们将只能获取 test 这个schema 下的表的字段
            // 具体请参照: http://stackoverflow.com/questions/38557956/databasemetadatagetcolumns-returns-an-empty-resultset

            if (sourceType == SourceType.MYSQL || sourceType == SourceType.GBASE) {
                resultSet = metaData.getColumns(schema, null, table, "%");
            } else {
                resultSet = metaData.getColumns(null, schema, table, "%");
            }

            while (resultSet.next()) {
                // TableFieldInfo 是一个POJO
                TableFieldInfo tableFieldInfo = new TableFieldInfo();
                tableFieldInfo.name = resultSet.getString(COLUMN_NAME);
                tableFieldInfo.type = resultSet.getString(TYPE_NAME);
                tableFieldInfo.defaultValue = resultSet.getObject(COLUMN_DEF);
                tableFieldInfo.length = resultSet.getInt(COLUMN_SIZE);
                Map map = tableFieldInfo.toMap();
                list.add(map);
            }
        } catch (Exception e) {

        }
        return list;
    }

    // 获取 jdbc url 下所有有权限访问的 schema
    // 本来没有这个方法的，在最后处理到 Hive 时，没有办法了，才加了这个方法
    public List<String> getSchemas(final BiDataSource dataSource) {
        List<String> schemaList = new ArrayList<>();

        try (Connection connection = DriverManager.getConnection(dataSource.url, dataSource.username, dataSource.password)) {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSet schemaSet = dbMetaData.getSchemas();
            while (schemaSet.next()) {
                schemaList.add(schemaSet.getString(TABLE_SCHEMA));
            }
        } catch (Exception e) {
            System.err.println("[ERROR]");
        }

        return schemaList;
    }

    // 获取指定 schema 下的所有表
    // 本来没有这个方法的，在最后处理到 Hive 时，没有办法了，才加了这个方法
    // 不过，这个方法被下面的同名方法给取代了，但也没有删除此方法。
    public List<String> getTables(final BiDataSource dataSource, String schemaName) {
        List<String> tableList = new ArrayList<>();

        try (Connection connection = DriverManager.getConnection(dataSource.url, dataSource.username, dataSource.password)) {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            final String schema = escapeWildcard(schemaName, dbMetaData.getSearchStringEscape());

            ResultSet tableRet;

            SourceType sourceType = this.getSourceType();
            // 此处处理，请参见上面 getTableFields() 方法的说明
            if (sourceType == SourceType.MYSQL || sourceType == SourceType.GBASE) {
                tableRet = dbMetaData.getTables(schema, null, "%", new String[]{TABLE});
            } else {
                tableRet = dbMetaData.getTables(null, schema, "%", new String[]{TABLE});
            }

            while (tableRet.next()) {
                tableList.add(tableRet.getString(TABLE_NAME));
            }
        } catch (Exception e) {
            System.err.println("[ERROR]");
        }

        return tableList;
    }

    // 获取指定 schema 下的所有表
    // 本来没有这个方法的，在最后处理到 Hive 时，没有办法了，才加了这个方法
    // 与上面的方法基本一样，该方法可以同时处理多个 schema
    public List<Pair<String, String>> getTables(final BiDataSource dataSource, List<String> schemas) {
        // pair: {key: schema, value: table}
        List<Pair<String, String>> tableList = new ArrayList<>();

        try (Connection connection = DriverManager.getConnection(dataSource.url, dataSource.username, dataSource.password)) {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            for (String schemaName : schemas) {

                final String schema = escapeWildcard(schemaName, dbMetaData.getSearchStringEscape());

                ResultSet tableRet;

                SourceType sourceType = this.getSourceType();
                try {
                    // 此处处理，请参见上面 getTableFields() 方法的说明
                    if (sourceType == SourceType.MYSQL || sourceType == SourceType.GBASE) {
                        tableRet = dbMetaData.getTables(schema, null, "%", new String[]{TABLE});
                    } else {
                        tableRet = dbMetaData.getTables(null, schema, "%", new String[]{TABLE});
                    }

                    while (tableRet.next()) {
                        tableList.add(new Pair(schemaName, tableRet.getString(TABLE_NAME)));
                    }
                } catch (SQLException e) {
                    System.err.println("[ERROR]");
                }
            }
        } catch (Exception e) {
            System.err.println("[ERROR]");
        }

        return tableList;
    }
}
